//include/deal.II-translator/A-headers/distributed_0.txt
// ---------------------------------------------------------------------
//
// Copyright (C) 2009 - 2020 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE.md at
// the top level directory of deal.II.
//
// ---------------------------------------------------------------------


/**
 *    @defgroup distributed Parallel computing with multiple processors using
 * distributed memory
 * @ingroup Parallel
 * @brief A module discussing the use of parallelism on distributed memory
 * 集群。 @dealiiVideoLecture{39,41,41.25,41.5} <h3>Overview</h3>
 * deal.II除了在 @ref
 * threads
 * 模块中讨论的共享内存机器内的并行化之外，还可以使用通过MPI连接的多台机器来并行化计算。基本上有两种方法可以利用多部机器。
 *
 *
 * - 每台机器都在本地保存整个网格和DoF处理程序，但每台机器上只保存全局矩阵、稀疏模式和解向量的一部分。
 *
 *
 * - 网格和自由度处理程序也是分布式的，也就是说，每个处理器只存储一部分单元和自由度。没有一个处理器知道整个网格、矩阵或解决方案，事实上，在这种模式下解决的问题通常非常大（比如，一亿到几十亿自由度），没有一个处理器可以或应该存储哪怕一个解决方案矢量。
 * 这两个选项中的第一个相对简单，因为人们在有限元程序中想做的大部分事情仍然以基本相同的方式工作，而且处理分布式矩阵、向量和线性求解器是很好的外部库，如Trilinos或PETSc，可以使事情看起来与在本地提供的一切几乎完全一样。这种并行化模式的使用在教程程序
 * step-17 和 step-18 中作了解释，这里不再详细讨论。
 * 真正的分布式网格的使用要复杂得多，因为它改变了一些可以用deal.II三角计算、DoF处理程序等完成的事情，或者使之成为不可能。本模块以离地面50000英尺的有利位置记录了这些问题，而不涉及太多的细节。下面描述的所有算法都在命名空间
 * parallel::distributed. 的类和函数中实现。
 * 在使用MPI的并行计算中，一个重要的方面是，对矩阵和向量元素的写访问需要在操作结束后和使用对象前（例如从读取）调用compress（）。也请参见
 * @ref GlossCompress  。 <h4>Other resources</h4>
 * 本命名空间中使用的算法的完整讨论，以及这里使用的许多术语的彻底描述，可以在 @ref distributed_paper  "分布式计算论文 "
 * 中找到。特别是，该论文表明，本模块讨论的方法可以扩展到数千个处理器和远远超过10亿个自由度。这篇论文还给出了许多术语的简明定义，这些术语在这里和图书馆的其他地方使用，与分布式计算有关。
 * step-40
 * 教程程序展示了该命名空间的类和方法在拉普拉斯方程上的应用，而
 * step-55 则是针对一个矢量值问题。   step-32 将 step-31
 * 程序扩展到大规模并行计算，从而解释了这里讨论的主题在更复杂应用中的使用。
 *
 *  <h4>Distributed triangulations</h4>
 * <table align="center"> <tr>
     <td> @image html distributed_mesh_0.png </td>
     <td> @image html distributed_mesh_1.png </td>
 * </tr> <tr>
     <td> @image html distributed_mesh_2.png </td>
     <td> @image html distributed_mesh_3.png </td>
 * </tr> </table>
 * 单元的颜色是基于 @ref GlossSubdomainId 的 "子域ID"
 * ，它标识了哪个处理器拥有一个单元：绿松石色代表处理器0，绿色代表处理器1，黄色代表处理器2，红色代表处理器3。可以看出，每个进程在自己的单元格周围都有一层幽灵单元格，这些单元格被正确地用子域ID着色，子域ID标识了拥有这些单元格的处理器。还要注意每个处理器是如何存储一些人工单元的，用蓝色表示，它们的存在只是为了确保每个处理器知道所有的粗网格单元，并且网格具有2:1的细化属性；然而，在这些人工单元所占据的区域，处理器不知道那里的网格到底有多细，因为这些区域是由其他处理器所拥有的。因此，我们将开发的所有算法只能在本地拥有的单元上运行，如果有必要，也可以在幽灵单元上运行；试图访问任何人工单元上的数据很可能是一个错误。注意，我们可以通过测试<code>cell-
 * @>subdomain_id()  ==
 * triangulation.local_owned_subdomain()</code>来确定我们是否拥有一个单元。
 * 这里需要考虑的 "真正的
 * "网格是由每个进程所拥有的单元组成的联合体，即由绿松石、绿色、黄色和红色区域的重叠所产生的网格，不考虑蓝色区域。
 *
 *
 * @note  这个 "真实
 * "的网格被分解成由每个进程存储的碎片，由<a
 * href="http://www.p4est.org">p4est</a>库提供。
 * p4est将完整的网格存储在一个叫做平行森林的分布式数据结构中（因此得名）。平行森林由四叉树（2D）或八叉树（3D）组成，这些树起源于每个粗略的网格单元，代表了从父单元到其四个（2D）或八个（3D）子单元的细化结构。在内部，这个平行森林由一个单元的（分布式）线性阵列表示，对应于每个树的深度优先遍历，然后每个进程存储这个单元的线性阵列的一个连续部分。这就导致了如上图所示的分区，从这个意义上说，它们不是最佳的，因为它们不能使子域之间的接口长度最小化（因此也不能使通信量最小化），但在实践中却非常好，可以用超快的算法进行操作。
 * 因此，以这种方式存储和操作单元的效率往往超过了通信的优化损失。
 * 这种划分方法产生的各个子域有时也可能由不相连的部分组成，如右上图所示）。然而，可以证明每个子域最多包括两个不相连的部分；见C.
 * Burstedde, J. Holke, T. Isaac: "Bounds on the number of discontinuities of
 * Morton-type space-filling curves", <a
 * href="http://arxiv.org/abs/1505.05055">arXiv 1505.05055</a>, 2017.)
 * <h4>Distributed degree of freedom handler</h4>
 * DoFHandler类建立在Triangulation类的基础上，但它可以检测到每当我们实际使用
 * parallel::distributed::Triangulation
 * 类型的对象作为三角形。在这种情况下，它为存在于全局网格上的所有自由度分配全局%数，但每个处理器将只知道那些定义在本地相关单元上的自由度（即本地拥有的单元或者是幽灵单元）。在内部，该算法的工作原理是：循环浏览我们本地拥有的所有单元，并为定义在这些单元上的自由度分配DoF指数，在不同处理器拥有的子域界面上的自由度，不属于邻近的处理器。然后，所有处理器交换他们本地拥有的自由度，并以这样的方式转移他们自己的指数，即所有子域上的每个自由度都由一个介于0和
 * DoFHandler::n_dofs()
 * 之间的指数唯一识别（这个函数返回全局自由度的数量，在所有处理器上累积）。请注意，在这一步之后，每个进程拥有的自由度形成了一个连续的范围，例如，可以通过
 * DoFHandler::locally_owned_dofs(). 返回的连续索引集得到。
 * 在为所有自由度分配了唯一的索引之后，
 * DoFHandler::distribute_dofs()
 * 函数就会在所有幽灵单元上循环，并与邻近的处理器进行通信，以确保这些幽灵单元上的自由度的全局索引与邻居分配给它们的索引一致。
 * 通过这个方案，我们可以确保我们本地拥有的每个单元以及所有的幽灵单元都可以被要求产生定义在它们身上的自由度的全局正确指数。然而，要求人造单元上的自由度很可能不会有什么好结果，因为这些单元没有任何信息（事实上，甚至不知道这些单元是否在全局网格上是活跃的，或被进一步细化）。
 * 像往常一样，自由度在被列举后可以被重新编号，使用命名空间DoFRenumbering中的函数。
 *
 *  <h4>Linear systems for %distributed computations</h4>
 * 在处理非常多的处理器时，人们很快就会了解到一件事，那就是不能在每个处理器上存储每个自由度的信息，即使这些信息是
 * "这个自由度不在这里"。这方面的一个例子是，我们可以为一个有
 * DoFHandler::n_dofs()
 * 行的（压缩）稀疏模式创建一个对象，但我们只填充那些对应于
 * DoFHandler::n_locally_owned_dofs()
 * 本地拥有的自由度的行。原因很简单：为了举例，我们假设我们有10亿个自由度，分布在100个处理器上；如果我们甚至在这个稀疏模式中每行只持有16个字节（无论我们是否拥有相应的自由度），即使每一行都是空的，我们也需要16GB的对象。当然，只有1000万行是不空的，为此我们需要160MB，再加上存储非零条目的实际列索引所需的东西。假设我们有一个中等复杂的问题，每行有50个条目，我们为每个条目存储价值4个字节的列索引，那么我们需要为1000万行中的每一行存储216个字节，以对应我们拥有的自由度，总共2.16GB。而我们不拥有的9.9亿行，每行需要16字节，共计15.840GB。很明显，如果我们使用更多的处理器，这个比例也不会变得更好。
 * 解决这个问题的方法是只对线性系统中我们拥有的部分使用任何内存，或者出于其他原因需要。对于所有其他部分，我们必须知道它们的存在，但我们不能设置我们数据结构的任何部分。为此，存在一个叫做IndexSet的类，它表示我们所关心的一组索引，我们可能要为其分配内存。稀疏模式、约束矩阵、矩阵和向量的数据结构可以用这些IndexSet对象进行初始化，以真正只关心那些与索引集中的索引相对应的行或条目，而不关心所有其他的索引。然后这些对象会询问集合中存在多少个索引，为每个索引分配内存（例如初始化稀疏模式的一行的数据结构），当你想访问全局自由度
 * <code>i</code> 的数据时，你会被重定向到用索引 <code>i</code>
 * 调用 IndexSet::index_within_set() 的结果，而不是。访问
 * IndexSet::is_element() 为假的元素 <code>i</code>
 * 的数据将产生一个错误。
 * 剩下的问题是如何确定与我们在每个处理器上需要担心的自由度相对应的指数集。为此，你可以使用
 * DoFHandler::locally_owned_dofs()
 * 函数来获取一个处理器拥有的所有指数。注意，这是定义在本地拥有的单元上的自由度的一个子集（因为两个不同子域之间的界面上的一些自由度可能被邻居拥有）。这个定义在我们拥有的单元上的自由度集合可以通过函数
 * DoFTools::extract_locally_active_dofs(). 得到。
 * 最后，有时我们需要本地拥有的子域以及相邻的幽灵单元上所有自由度的集合。这个信息由
 * DoFTools::extract_locally_relevant_dofs() 函数提供。
 *
 *  <h5>Vectors with ghost elements</h5>
 * 一个典型的并行应用要处理两种不同类型的并行向量：带有鬼魂元素的向量（也叫鬼魂向量）和没有鬼魂元素的向量。  这两种类型通常可以由同一数据类型表示，但当然也有不同的向量类型可以分别表示这两种类型：例如 TrilinosWrappers::MPI::Vector,   PETScWrappers::MPI::Vector, 和建立在这些之上的BlockVector对象）。你可以在 @ref GlossGhostedVector "关于重影向量的词汇表条目 "
 * 中找到关于区分这些类型的向量的讨论。
 * 另一方面，没有鬼魂项的向量在其他所有地方都可以使用，如组装、求解或任何其他形式的操作。这些通常是只写的操作，因此不需要对可能被另一个处理器拥有的向量元素进行读取访问。
 * 你可以使用operator=在有鬼魂元素和无鬼魂元素的向量之间进行复制（你可以在
 * step-40 ,  step-55  , 和  step-32  中看到）。
 *
 *  <h5>Sparsity patterns</h5>
 * 在写这篇文章的时候，唯一能够处理刚才解释的情况的类是DynamicSparsityPattern。该函数的一个版本
 * DynamicSparsityPattern::reinit()
 * 存在，它接受一个IndexSet参数，指示要为疏散模式的哪些行分配内存。换句话说，创建这样一个对象是安全的，它将报告其大小为10亿，但实际上只存储了索引集有多少个元素的行。然后你可以使用通常的函数
 * DoFTools::make_sparsity_pattern
 * 来建立疏散模式，该模式是在网格的本地拥有的部分进行装配而产生的。产生的对象可以用来初始化PETSc或Trilinos矩阵，这些矩阵通过完全分布式存储支持非常大的对象尺寸。然后，该矩阵可以通过只在当前处理器所拥有的单元上进行循环来进行组装。
 * 唯一需要注意的是，稀疏性需要存储哪些自由度的条目。从本质上讲，这些是我们在组装时可能在矩阵中存储的值。很明显，这些肯定是本地活动的自由度（它们生活在我们本地拥有的单元上），但是通过约束，也有可能写到位于幽灵单元上的条目。因此，你需要在初始化稀疏模式时传递来自
 * DoFTools::extract_locally_relevant_dofs() 的索引集。
 *
 *  <h4>Constraints on degrees of freedom</h4>
 * 在创建稀疏模式以及组装线性系统时，我们需要知道自由度的约束，例如由悬挂节点或边界条件导致的约束。像动态稀疏模式类一样，AffineConstraints容器在构造时也可以接受一个IndexSet，这个IndexSet指示在可能非常多的自由度中它应该实际存储哪些约束。与稀疏模式不同的是，这些自由度现在只是我们在组装时本地处理的自由度，即那些由
 * DoFTools::extract_locally_active_dofs()
 * 返回的自由度（本地拥有的自由度的超集）。
 * 然而，在有些情况下，更复杂的约束会出现在有限元程序中。一个例子是在
 * $hp$
 * 的适应性计算中，自由度可以针对其他自由度进行约束，而这些自由度本身也被约束。在这样的情况下，为了完全解决这个约束链，只存储局部活动自由度的约束可能是不够的，可能还需要有局部相关自由度的约束可用。在这种情况下，AffineConstraints对象需要用由
 * DoFTools::extract_locally_relevant_dofs()
 * 产生的IndexSet进行初始化。
 * 一般来说，如果你碰巧没有在每个处理器上存储所有必要的约束条件，你的程序将继续做一些事情：你将只是生成错误的矩阵条目，但程序不会中止。这与稀疏模式的情况相反：在那里，如果传递给DynamicSparsityPattern的IndexSet表明它应该存储太少的矩阵行，那么当你试图向不存在的矩阵条目写入时，程序会中止，或者矩阵类会默默地分配更多的内存来容纳它们。因此，在指明存储哪些约束条件时，谨慎行事是很有用的，使用
 * DoFTools::extract_locally_relevant_dofs() 的结果而不是
 * DoFTools::extract_locally_active_dofs()
 * 。这也是可以承受的，因为局部相关自由度的集合只比局部活动自由度的集合稍大一些。我们在
 * step-32 、 step-40 和 step-55 中选择了这种策略。
 *
 *  <h4>Postprocessing</h4>
 * 和其他一切一样，你只能对本地处理器拥有的单元格进行后处理。DataOut和KellyErrorEstimator类自动做到了这一点：它们只对本地拥有的单元格进行操作，不需要做任何特别的事情。至少对于大型计算来说，也没有办法在一台机器上合并所有这些本地计算的结果，也就是说，每个处理器必须是自给的。例如，每个处理器必须生成自己的并行输出文件，而这些文件必须由一个能够处理多个输入文件的程序进行可视化处理，而不是在生成一个单一的输出文件之前将调用DataOut的结果合并到一个处理器。后者可以实现，例如，使用
 * DataOutBase::write_vtu() 和 DataOutBase::write_pvtu_record() 函数。
 * 这些考虑同样适用于所有其他的后处理动作：例如，虽然有可能通过在本地进行计算并将产生的单个数字处理器累积为整个通信的单个数字来计算全局能量耗散率，但如果每个处理器产生的数据量很大，一般来说是不可能做到这一点。
 * 然而，对于后处理有一个特别的考虑：无论你在一个处理器拥有的每个单元上做什么，你至少需要访问在这些单元上活跃的所有那些解向量的值（即访问所有<i>locally active
 * degrees of freedom</i>的集合，用 @ref distributed_paper
 * 《分布式计算论文》的语言），这是这个处理器实际拥有的自由度的超集（因为它可能不拥有自己的单元和其他处理器拥有的那些单元之间界面上的所有自由度）。然而，有时你需要更多的信息：例如，为了计算KellyErrorIndicator的结果，我们需要评估当前和邻近单元的界面上的梯度；后者可能为其他处理器所拥有，所以我们也需要这些自由度。因此，一般来说，人们需要获得所有自由度为<i>locally
 * relevant</i>的解值。另一方面，我们可以用于并行线性代数的两个包（PETSc和Trilinos）以及
 * parallel::distributed::Vector
 * 都将向量细分为每个处理器拥有的块和存储在其他处理器的块。因此，要对东西进行后处理意味着我们必须告诉PETSc或Trilinos，它也应该导入<i>ghost
 * elements</i>，即除了我们本地拥有的向量之外，还应该导入解向量的其他向量元素。对于重影向量，这可以通过使用以分布式向量为参数的operator=来实现。
 *
 */



namespace parallel
{
  /**
   * 一个命名空间，用于支持%分布式内存机器上的%并行计算的类和函数。参见 @ref
   * 分布式模块，以了解该命名空间提供的设施的概况。
   * @ingroup distributed
   *
   */
  namespace distributed
  {
  }
}


